Туторіал Android RecyclerView з Kotlin
Зауваження щото оновлення: Цей туторіал був оновлений Rod Biresch для Kotlin, Android 26 (Oreo) та Android Studio 3.0. Оригінальний туторіал був написаний Darryl Bayliss.
Ресайклінг
(ака повторне використання) є одною з тих речей, що корисна
для планети, та є логічним способом переконатись, що ми не
потонемо у власному смітті, або залишимось без ресурсів в
майбутньому.
Декілька Android інженерів подумали про вигоди ресайклингу
та уявили, що OS може виконуватись більш ефективно, якщо
буде повтороно використовувати ресурси. Результатом цього
натхнення були мільйони еко-воїнів та ентузіастів
ресайклінгу, коли віджет RecyclerView
був
включений до Android Lollipop — або десь так. :]
Ще більше свято було, коли Google анонсував бібліотеку підтримки, що робить це чистою, зеленою машиною ресайклингу, зворотньо сумісною з усіма версіями аж до Android Eclair (2.2), що був виданий аж у 2010му році!
В цьому туторіалі ви отримаєте досвід щодо використання RecyclerView в дії, а саме навчитесь щодо наступного:
- Призначення RecyclerView
- Компоненти, з яких складається RecyclerView
- Як змінити розміщення RecyclerView
- Як додати деякі милі анімації до вашого RecyclerView
Також ми завітаємо у відкритий космос, разом з прикладом застосування Galacticon. Ви будете будувати свій потік зі щоденних астрономічних світлин з публічного NASA API.
Попередні вимоги: Вам треба мати робочі навички з робробки для Android з Kotlin, перед проходженням цього туторіала. Якщо вам треба оновити знаяння, подивіться на наші вступні туторіали! Також вам знадобиться Android Studio 3.0 або новіша.
Прямуємо на мис Канаверал: починаємо
Завантажте початковий проект та відкрийте його в Android Studio. Це ще не багато що робить, та всемогутній RecyclerView нема де бачити.
Клацніть на кнопці Run app зверху, та побачите
дещо, що представляє відкритий космос в дещо невірний
спосіб:
Він пустий, але це недобре. Ви не дуже чомусь навчитесь, якщо за вас буде зроблена вся робота! Перед тим, як ви зможете додати чудову астрофоторафію від NASA, вам знадобиться виконати деякі налаштування.
Отримання API ключив до Shuttle
Ви будете використовувати Astronomy Picture of the Day API, один з найбільш популярних веб сервісів, запроваджених NASA. Щоб переконатись, що вони не стануть жертвами небажаного трафіку, сервіс вимагає від вас мати ключ API, що буде застосовуватись з вашим застосуванням.
На щастя, отримання ключа таке просте, к надання вашого ім'я та адреси електронної пошти до api.nasa.gov, та копіювання ключа API, що з'явиться на екрані або (не або, а також - прим.перекл) на вашу електронну пошту.
Як тільки ви отримаєте ваш ключ API, скопіюйте його,
відкрийте файл strings.xml в вашому проекті, та
вставьте ваш ключ API як текстовий ресурс api_key
,
заміщуючи INSERT API KEY HERE
:
Космічна чудасія: вивчаємо RecyclerView
Ви готові для запуску у відкритий космос, щоб дослідити величність RecyclerViews, але некомпетентний командер прямуватиме до невідомого без підготування. Ви маєте запитання, та вам потрібні відповіді, перед тим, як іти далі. Дивіться на цей розділ, як на брифінг перед міссією.
На RecyclerView можна дивитись як на комбінацію ListView та GridView. Однак, існують додаткові можливості, що поділяє ваш код на керовані компоненти, так само, як вони використовують ефективні до пам'яті шаблони дизайну.
Але як це було б краще за спробувані та протестовані ListView та GridView, які ми вже використовували? Чи це якась технологія прибульців? Відповіді, як завжди, криються в деталях.
Чому вам потрібен RecyclerView
Уявіть, що ви створили ListView, де показати власні документи є досить складним. Займе деякий час мило створити рядкове розташування для ціх елементів, та потім використати це розташування в вашому адаптері.
В вашому методі getView()
ви розкладуєте ваше
нове розташування елементу. Поітм ви посилаєтесь на кожний
елемент всередині, через використання унікальних
ідентифікаторів, що ви провадите в вашому XML, щоб
налаштувати та додати деяку логіку відображення. Коли все
буде скінчено, ви передаєте цей елемент до ListView, що
готовий відображувати це на екрані. Все гарно…чи не так?
Правда полягає в тому, що ListViews та GridViews роблять
тільки половину роботи по досягненню дійсної ефективності по
пам'яті. Вони повторно використовують макет
елементу, але не мають посилання на дітей розташування,
примушуючи до виклику findViewById()
для
кожного елементу розташування, кожного разу при виклику getView()
.
Всі ці виклики можуть стати дуже процесор-інтенсивними, особливо для складних розташувань. Більше цього, ситуація може призвести до того, що прокрутка вашого ListView стане сіпаною, або нереагуючою, бо він нестямно намагається займати ресурси для запитаних вами елементів.
Інженери Android з самого початку провадили рішення до цієї
проблеми на сайті Android Developers за допомогою гладкого
прокручування, через потужний шаблон View
Holder
.
Коли ви використовуєте цей шаблон, ви створите клас, що
стає посиланням в пам'яті на всі елементи, потрібні для
вашого розташування. Вигода в тому, що якщо ви встановите
посилання один раз, та потім будете використовувати їх,
ефективно є обхідним шляхом щодо пікового навантаження, що
випливає від постійних викликів findViewById()
.
Проблема в тому, що це опціональний шаблон для ListView або GridView. Якщо ви не підозрювали про ці деталі, тоді ви можете здивуватись, чому ваші ListViews та GridViews такі повільні.
Перший контакт: RecyclerView та розташування
Поява RecyclerView змінила все. Він використовує Adapter, щоб діяти як джерело даних; однак, ви маєте створити ViewHolders для утримання посилань в пам'яті.
Коли вам треба новий елемент, він або створює новий об'єкт ViewHolder для розгортання розташування, та зберігати ці посилання, або буде повторно використаний такий з існуючого стеку.
Тепер ви знаєте, чому він названий RecyclerView!
Інший прибуток з використання RecyclerViews є в тому, що він іде з анімаціями по замовчанню, що ви не створювали та додавали самотуж — вони просто роблять.
Дякуючи потребі в ViewHolder, RecyclerView точно знає, яку анімацію застосовувати до кожного елементу. Та що краще за все, він просто робить, як треба. Ви навіть можете створити ваші власні анімації, та застосувати їх, якщо виникне потреба.
Останній, та найбільш цікавий компонент RecyclerView, це його LayoutManager. Цей об'єкт роміщує елементи RecyclerView, та каже йому, коли повторно використовувати елементи, що перемістились за межі екрарну.
Менеджери розташування надходять в трьох варіантах по замовчанню:
- LinearLayoutManager позиціонює ваші елементи як стандартний ListView
- GridLayoutManager розміщує ваші елементи в решитці, подібно до GridView
- StaggeredGridLayoutManager позиціонує елементи в форматі зсунутої сітки.
Також ви можете створити ваш власний LayoutManagers
для використання в RecyclerView
, якщо ви
бажаєте додатковий рівень налаштування.
Маємо надію, що це відповідає на всі ваші питання, командер. Тепер час розпочати місію!
Підготовка до запуску: створення RecyclerView
Щоб створити RecyclerView ми розіб'ємо роботу на чотири частини:
- Декларувати RecyclerView в розміщенні макету, та зробити посилання на нього в нашому файлі Kotlin.
- Створити власний елемент XML розташування для вашого RecyclerView, що буде використовуватись для елементів.
- Створити зберігач елементів для ваших елементів перегляду, перехопити джерело даних RecyclerView, та ообробити логіку перегляду, через створення адаптеру RecyclerView Adapter.
- Під'єднати адаптер до RecyclerView.
Перший крок має бути знайомим. Відкрийте файл розташування activity_main.xml, та додайте наступне як дитину до LinearLayout:
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
Тут ми встановлюємо розташування, та кажемо RecyclerView зайняти весь батьківський контейнер.
Зауваження: Ви використовуєте бібліотеку підтримки v7 для зворотньої сумісності зі старішими пристроями. Стартовий проект вже додає RecyclerView Support Library як залежність до вашого застосування в файлі build.gradle. Якщо ви бажаєте більше інформації як це зробити самотуж, клацніть на сайті Android developer.
Відкрийте MainActivity.kt, та декларуйте наступну властивість сверху класу:
private lateinit var linearLayoutManager: LinearLayoutManager
В onCreate()
додайте наступні рядки після setContentView
:
linearLayoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = linearLayoutManager
Android Studio має надати підказку для імпорту kotlinx.android.synthetic.main.activity_main.*
для recyclerView
. Ви можете здивуватись, як ми
збираємось посилатись на recyclerView
, зпершу
не пошукавши елемент за допомогою findViewById()
?
Проект був сконфігурований за допомогою плагіна Kotlin Android Extensions.
Цей плагін дозволяє імпортувати елементи в розташуванні як
“синтетичні” властивості.
import kotlinx.android.synthetic.main.activity_main.*
Тепер recyclerView
є розширеною властивостю
для Activity
, та вона має той самий тип, як
декларовано в activity_main.xml
. Плагін
видаляє багато зайвого коду, та зменьшує ризик потенційних
помилок.
Фаза запалювання завершена! Ви декларували та розмістили пам'ять для двох частин пазла, що потрібні RecyclerViews для роботи: самий RecyclerView та його Layout Manager.
Фаза запалювання 2: розташування для елементів RecyclerView
Друга фаза запалювання включає створення власного розташування для елементів, що ви бажаєте використовувати в вашому RecyclerView. Це робить так само, як це було, коли ви створювали розташування для ListView або Gridview.
Прямуйте до вашої директорії розташувань, та створіть нове
розташування на ім'я recyclerview_item_row
, та
встановіть кореневий елемент до LinearLayout
.
В вашому наступному розташуванні додайте наступні XML
елементи як дітей до вашого LinearLayout:
<ImageView
android:id="@+id/itemImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:layout_weight="3"
android:adjustViewBounds="true" />
<TextView
android:id="@+id/itemDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:layout_marginTop="8dp"
android:layout_weight="1"
tools:text="Some date" />
<TextView
android:id="@+id/itemDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|start"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="5" />
Це не космічна наука: ви створили декілька елементів як дитяці до вашого розташування, які ви тепер можете використати в вашому адаптері.
Адаптери: ракетне паливо для вашого RecyclerView
Клацніть правою кнопкою миші на вашому каталозі com.raywenderlich.galacticon,
оберіть New \ Kotlin File/Class, та назвіть його RecyclerAdapter
та обоеріть Class для Kind. Зверху файлу, під
декларацією package
, імпортуйте бібліотеку
підтримки RecyclerView:
import android.support.v7.widget.RecyclerView
Зробіть клас розширенням RecyclerView.Adapter, так, щоб він виглядав наступним чином:
class RecyclerAdapter : RecyclerView.Adapter<RecyclerAdapter.PhotoHolder>() {
}
Android Studio здогадається, що ви розширюєте клас, що потребує методів, та буде підкреслювати декларацію вашого класу червоним.
Щоб вирішити це, клацніть на рядку коду щоб вставити ваш курсор, потім натисніть Option + Return (або Alt + Enter на PC), щоб отримати контекстне меню. Оберіть Implement Methods:
Підтвердіть, що ви бажаєте реалізувати підказані методи, та клацніть на OK:
Ці методи є рушійною силою позаду вашого адаптора
RecyclerView. Зауважте, що ви все ще маєте помилку
компілятора на цей момент – це тому, що ваш адаптер та
потрібні миетоди насправді визначені з ваикористанням класу
ViewHolder, PhotoHolder
, що досі не існує. Ви
отримаєте ваш ViewHolder, та скорочено побачите, що робить
кожний з методів, так що тримайтесь, Командере!
Як з кожиним адаптером, вам треба запровадити відповідному елементу спосіб заповнення свої поля, та вирішити, скількі елементів загалом має бути.
Клацання по елементу раніше обороблялись ListView або
GridView в onItemClickListener
. RecyclerView
не провадить методів як ці, оскільки він має єдине
призначення: переконатись, що елементи всередині правильно
позиційоновані, та керуються ефективно.
Робота по слуханню за діями тепер передана до елелмента RecyclerView, та його дітей. Це може виглядати більше як навантаження, але з іншого боку, ви отримуєте гарно налаштований контроль над тим, як ваші дитячі елементи мають діяти.
Зверху вашого класу RecyclerAdapter додайте змінну photos
,
що міститиме ваші сввітлини в первинному конструкторі:
class RecyclerAdapter(private val photos: ArrayList<Photo>) RecyclerView.Adapter<RecyclerAdapter.PhotoHolder>() {
Гарна робота, Командер! Ваш адаптер тепер знає, де шукати дані. Скоро ви отримаєте ArrayList зі світлин, з кращими астрофотографіями!
Далі ви заповнюєте згенеровані методи, що були додані Android Studio.
Перший метод, getItemCount()
, є досить
простим, та має бути знайомий по вашій роботі з ListViews
або GridViews.
Адаптер буде визначати, скільки елементів відображувати. В
цьому випадку ми бажаємо, щоб адаптер показував кожне фото,
що ми завантажили з NASA API. Щоб зробити це, додайте
оновлення getItemCount()
наступним чином:
override fun getItemCount() = photos.size
Далі ми збираємось досліджувати шаблон ViewHolder
,
щоб зробити об'єкт, що зберігає всі посилання на елементи
керування.
Липучка для всіх: зберігання наших елементів керування
Щоб створити PhotoHolder для посилання на ваші елементи, ви створите вкладений клас в вашому адаптері. Ви додасте його тут, замість ніж в окремому класі, оскільки його поведінка близько пов'яззана з адаптером. Перше, імпортуємо синтетичні властивості для ресайклера, так що ви можете посилатись на властивості елементів:
import kotlinx.android.synthetic.main.recyclerview_item_row.view.*
Додайте наступний код в кінець класу RecyclerAdapter:
//1
class PhotoHolder(v: View) : RecyclerView.ViewHolder(v), View.OnClickListener {
//2
private var view: View = v
private var photo: Photo? = null
//3
init {
v.setOnClickListener(this)
}
//4
override fun onClick(v: View) {
Log.d("RecyclerView", "CLICK!")
}
companion object {
//5
private val PHOTO_KEY = "PHOTO"
}
}
Так що ми тут робимо?
- Робимо клас, що розширює RecyclerView.ViewHolder, дозволяючи використовувати його як ViewHolder для адаптера.
- Додаємо посилання на життєвий цикл об'єкта, що дозволяє ViewHolder підвішуватись до вашого View, так що він має доступ до ImageView та TextView, як до розширених властивостей. Плагін Kotlin Android Extensions додає приховані функції та поля кешування, так що елементи не запитуютсья кожного разу.
- Ініціалізуємо
View.OnClickListener
. - Реалізуємо потрібіний метод для
View.OnClickListener
, оскільки ViewHolder відповідальні за обробку власних подій. - Додаємо ключ для простішого посилання на окремий елемент, що був використаний для запуску RecyclerView.
Ви все ще матимете помилки компілятора з методвами onBindViewHolder
та onCreateViewHolder
. Змініть аргумент holder:
?
до onBindViewHolder
, щоб він мав
тип RecyclerAdapter.PhotoHolder
.
override fun onBindViewHolder(holder: RecyclerAdapter.PhotoHolder, position: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
Тепер додайте тип значення RecyclerAdapter.PhotoHolder
до метода onCreateViewHolder
, та видаліть
оператор безпечного виклику (тобто ?
) з типу
аргумента parent
.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.PhotoHolder {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
Тепер ви можете бути в змозі побудувати та виконати застосування, але воно буде виглядати так само, оскільки ми досі не сказали RecyclerView, як асоціювати PhotoHolder з переглядом.
Збираємо частини
Іноді немає доступних ViewHolders. В такому сценарії
RecylerView буде викликати onCreateViewHolder()
з RecyclerAdapter, щоб створити такий. Ви будете
використовувати розташування елементу — PhotoHolder — щоб
створити перегляд для ViewHolder.
Код розташування може бути просто бути доданий до onCreateViewHolder()
.
Однак, це гарна можливість показати дійсно круту можливість
Kotlin, що називається Розширення.
Зпочатку, додайте новий файл Kotlin на ім'я Extensions.kt до проекту, та потім додайте наступну нову функцію розширення до цього файлу:
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
}
Замістіть рядок TODO("not implemented")
між
фігурними дужками в onCreateViewHolder()
на
наступне:
val inflatedView = parent.inflate(R.layout.recyclerview_item_row, false)
return PhotoHolder(inflatedView)
Тут мі формуємо перегляд з розташування, та передаємо його
до PhotoHolder. Метод parent.inflate(R.layout.recyclerview_item_row,
false)
буде виконувати нову функцію-розширення ViewGroup.inflate(...)
,
щоб розмістити це розташування.
Та маючи це, ви вже зробили те, що об'єкти, які зберігають ці посилання, будуть використані повторно, але є ще декілька частин, які треба зтулити докупи, перед тим, як ви зможете запустити вашу ракету.
Почнемо нову активність, через заміну журналу в ViewHolder
onClick
цім кодом :
val context = itemView.context
val showPhotoIntent = Intent(context, PhotoActivity::class.java)
showPhotoIntent.putExtra(PHOTO_KEY, photo)
context.startActivity(showPhotoIntent)
Це захоплює поточний контекст вашого елементу перегляду, та створює намір, щоб показати нову активність на екрані, передаючи фото об'єкт, який ви бажаєте відобразити. Передача об'єкту контекста в намір дозволяє застосуванню знати, яку активність ми полишаємо.
Наступна річ, це додати наступний метод в PhotoHolder
:
fun bindPhoto(photo: Photo) {
this.photo = photo
Picasso.with(view.context).load(photo.url).into(view.itemImage)
view.itemDate.text = photo.humanDate
view.itemDescription.text = photo.explanation
}
Це прикріплює фото до PhotoHolder, даючи вашому елементу дані, що треба для з'ясування, що треба відображувати.
Це також додає рекомендований імпорт Picasso, що є бібліотекою, яка значно спрощує отримання зображень по певному URL.
Остання частина збірки PhotoHolder скаже йому, як
показувати правильне фото в правильний момент. Це
RecyclerAdapter onBindViewHolder
, та він
дозволяє вам знати, коли новий елемент буде доступний на
екрані, та холдер потребує деяких даних.
Додамо наступний код в метод onBindViewHolder()
:
val itemPhoto = photos[position]
holder.bindPhoto(itemPhoto)
Тут ви передаєте копію вашого ViewHolder та позицію, де
елемент буде показаний в вашому RecyclerView, та виклик bindPhoto(...)
.
І це все, що вам треба зробити в цій збірці — просто використовуйте позицію, де ваш ViewHolder буде з'являтись, щоб захопити фото з вашого списку, та потім передати її до ViewHolder.
Третій крок нашого протоколу перевірки до запуску завершений!
Відлік та відрив: пов'язуємо Adapter та RecyclerView
Це тей момент, на який ми чекали, фінальна частина перед запуском! Все що нам треба, це пов'язати ваш адаптер до вашого RecyclerView, та переконатись, що він отримує фото, коли він створений, так що ви можете досліджувати всесвіт — по малюнках.
Відкрийте MainActivity.kt, та додайте цю властивість в самому :
private lateinit var adapter: RecyclerAdapter
Далі, під присвоєнням до recyclerView.layoutManager
додайте наступне:
adapter = RecyclerAdapter(photosList)
recyclerView.adapter = adapter
Тут ми створюємо адаптер, передаючи його до конструкторів, яких треба, та встановлюючи його як адаптер для вашого RecyclerView.
Хоча адаптер і під'єднаний, існує ще одна річ, щоб переконатись, що ми не матимемо пустий екран.
В onStart()
, під викликом super
додаємо цей код, :
if (photosList.size == 0) {
requestPhoto()
}
Це додає перевірку, чи не є ваш список порожнім, та якщо так, він запитує фото.
Далі, в receivedNewPhoto()
, оновіть метод,
так, щоб він виглядав наступним чином:
override fun receivedNewPhoto(newPhoto: Photo) {
runOnUiThread {
photosList.add(newPhoto)
adapter.notifyItemInserted(photosList.size)
}
}
Тут ми інформуємо адаптер ресайклера, що елемент доданий після того, як список фото був оновлений.
Тепер ви готові розпочати стартовий відлік.. тобто запустити застосування.
Зпустіть застосування, запустіть емулятор, та з часом Galacticon повинен виглядати дещо подібне до цього:
Це не все. Тицьніть в фото, та ви повинні опинитись на новій активності, що приймає цей елемент в фокусі:
Але це все ще не все! Спробуйте повернути ваши пристрій 'або емулятор (function + control + F11/F12), та ви побачите зображення на повному екрані!
В залежності від розміру зображення та екрана вашого пристрою, це може виглядати дещо деформованим, але не турбуйтесь про це.
Наші вітання! Ви маєте роблячий RecyclerView, та можете робити ваші подорожі до зірок.
Космічні кроки: додавання підтримки прокручування
Якщо ви повернетесь до MainActivity на вашому пристрої, та зпробуєте прокрутити донизу, ви побачите, що вам дечого не вистачає — ваш RecyclerView не отримує жодних нових фото.
Ваш RecyclerView робить точно як ви йому наказали,
показуючи вміст photosList
. Проблема в тому,
що застосування буде отримуват лише одне фото, коли ви
заввантажуєте застосування. Воно не має і гадки, як отримати
більше фото.
Так що далі ми будемо отримувати декілька фото, та індекс останнього фото при прогортуванні. Тепер ви перевірите, чи останнє фото видиме, чи немає більше фото в запиті. Якщо обоє дають true, тоді ваше застосування іде на сайт, та завантажує більше гарних фото!
Ця латка буде потребувати космічної ходи, так що розбивайте ваш скафандр, та налаштуйтесь отримати доствід невагомості.
В MainActivity.kt додайте властівість з власним асессором знизу MainActivity:
private val lastVisibleItemPosition: Int
get() = linearLayoutManager.findLastVisibleItemPosition()
Це використовує ваш LinearLayoutManager з RecyclerView для отримання індексу останнього видимого елементу на екрані.
Далі ви додаєте метод, що вставляє onScrollListener
в
ваш RecyclerView, так що ви отримуєте зворотній
виклик, коли користувач використовує прокрутку:
private fun setRecyclerViewScrollListener() {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val totalItemCount = recyclerView!!.layoutManager.itemCount
if (!imageRequester.isLoadingData && totalItemCount == lastVisibleItemPosition + 1) {
requestPhoto()
}
}
})
}
Ця функція встановлює слухача прокрутки в RecyclerView, що спрацьовує за дій користувача. Під час прокручування, слухач отримує число елементів в LayoutManager, та обчислює індекс останнього видимого фото. Коли це зроблене, він опрівнює ці числа(збільшуючи індекс на 1, оскільки індекс починається з 0, тоді як лічильник починається з 1). Якщо вони співпадають, та немає фото, що вже в запиті, тоді викликається запит нового фото.
Нарешті, з'єднаємо все до RecyclerView, викликавши метод в
onCreate
, прямо під тим, як ми встановили ваш
адаптер RecyclerView:
setRecyclerViewScrollListener()
Застрибуйте назад до космічного корабля (побудуйте та запустіть застосування знову). Прокрутіть донизу, та ви маєте побачити покращення!
Чудова робота, тепер ваш RecyclerView оновлюється, щоб
відобразити останні фото, запитані вашим застосуванням.
Велика річ в тому, що receivedNewPhoto()
обробляє більшість з роботи, оскільки ви сказали йому
повідомляти ваш адаптер щодо нових елементів.
Це додає інтергалактичних схвалень для ресайклінгу кода!
Зміна розташування
Тепер, коли ваш RecyclerView налаштований та робить, час побавитись з вашим космічним кораблем.
Чи не було б це круто, якщо б RecyclerView міг змінювати власне розташування? Гарна новина: позиціювання елементів RecyclerView виділене в менеджер розташування.
Додайте властивість GridLayoutManager нагорі MainActivity.kt:
private lateinit var gridLayoutManager: GridLayoutManager
Зауважте, що GridLayoutManager є вбудованим менеджером розташування, але це так саме гарно може бути ваш власний.
В onCreate()
ініціалізуйте LayoutManager під
існуючим Linear Layout Manager:
gridLayoutManager = GridLayoutManager(this, 2)
Так само, як раніше ви робили з LayoutManager, ви передаєте контекст, в якому менеджер має робити, але на відміну від цього, він також приймає цілий параметр. В цьому випадку ви встановлюєте число стовпчиків, що матиме решітка.
Додайте цей метод до MainActivity:
private fun changeLayoutManager() {
if (recyclerView.layoutManager == linearLayoutManager) {
//1
recyclerView.layoutManager = gridLayoutManager
//2
if (photosList.size == 1) {
requestPhoto()
}
} else {
//3
recyclerView.layoutManager = linearLayoutManager
}
}
Цей код перевіряє, який LayoutManager використовує ваш RecyclerView, та потім:
- Якщо він використовує LinearLayoutManager, він змінюється на GridLayoutManager
- Він запитує нове фото, якщо ваша решітка відображує лише одне фото
- Якщо використовується GridLayoutManager, він перемикається на LinearLayoutManager
Далі вам треба зробити деякі зміни до lastVisibleItemPosition
,
щоб допомогти обробці новому LayoutManager. Зробіть щоб він
виглядав приблизно так:
private val lastVisibleItemPosition: Int
get() = if (recyclerView.layoutManager == linearLayoutManager) {
linearLayoutManager.findLastVisibleItemPosition()
} else {
gridLayoutManager.findLastVisibleItemPosition()
}
Тут ми запитуємо RecyclerView повідомити вам, який його LayoutManager, та потім запитуємо, що цей LayoutManager каже щодо позиції останнього видимого елементу.
Щоб вімкнути розташування-решітку, використайте меню
Options, що вже доступне в вашому застосуванні. Додайте
наступний код після onStart()
:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_change_recycler_manager) {
changeLayoutManager()
return true
}
return super.onOptionsItemSelected(item)
}
Це перевіряє ID обраного елементу, та потім робить, що треба зробити. В цьому випадку буде тільки один ID, який ми перевіримо, що ефективно каже застосуванню піти та змінити LayoutManager.
Та коли це вже є, ви готові до запуску! Завантажте застосування, та клацніть на кнопці зверху зправа на екрані, і маєте побачити, як зорі починають рухатись:
Вбивця зірок
Іноді ви бачите речі, які ви просто не бажаєте бачити, скажімо, як далека-далека галактиеа переходить на темну сторону, або на планету, що обречена на знищення. Як щодо знищити це за допомогою свайпа (висування вбік)?
На щастя, інженери Android запровадили корисний клас з
назвою ItemTouchHelper
, що надає вам просту
поведінку свайпу. Створення та приєднання його до
RecyclerView потребує тільки декількох рядків коду.
В MainActivity.kt, під setRecyclerViewScrollListener()
додайте
наступний метод :
private fun setRecyclerViewItemTouchListener() {
//1
val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, viewHolder1: RecyclerView.ViewHolder): Boolean {
//2
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
//3
val position = viewHolder.adapterPosition
photosList.removeAt(position)
recyclerView.adapter.notifyItemRemoved(position)
}
}
//4
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
}
Пройдемо по цьому крок за кроком:
- Ви можете створити зворотній виклик, та вказати, які події слід слухати. Він сприймає два параметри, один для напрямку перетягування, та один для напрямків свайпу, але ви зацікавлені тільки в свайпі, так що ви передаєте 0, щоб поінформувати зворотній виклик не відповідати на події перетаскування.
- Ви повертаєте false в
onMove
, оскільки ви не бажаєте виконувати тут жодної особливої поведінки. onSwiped
викликається, коли елемент свайпиться в напрямку, вказаному вItemTouchHelper
. Тут ви запитуєте параметрviewHolder
, переданий вам як позиція в перегляді елементів, потім ви видаляєте цей елемент з списку фотографій. Нарешті ви інформуєте адаптер RecyclerView, що елемент в певній позиції був видалений.- Ви ініціалізуєте
ItemTouchHelper
визначеною вами поведінкою зворотнього виклику, та потім додаєте його до RecyclerView.
Додайте метод то onCreate()
активності, під setRecyclerViewScrollListener()
:
setRecyclerViewItemTouchListener()
Це прикріплить ItemTouchListener
до
RecyclerView, використовуючи код, що ви щойно написали.
Виконайте застосування ще раз, та зробіть свайп вбік по одному з ваших елементів, ви маєте побачити, що він почне рухатись. Якщо ви протягнете його досить далеко, ви побачите, як він анімовано знике. Якщо інші елементи видимі, вони реорганізуються самі, щоб закрити дірку. Ци є щось крутіше?
Куди рухатись далі?
Гарна робота! Ви побували в деяких пригодах, але зараз час повертатись назад на Землю, та подумати про те, чому ми навчимось.
- Ви створили RecyclerView, та всі необхідні компоненти, такі, як LayoutManager, Adapter та ViewHolder.
- Ви оновляєте та видаляєте елементи з адаптеру.
- Ви додали деякі круті можливості, як зміну розташувань, та додали функціональний свайп.
Окрім всього, ви навчились, як розділяти компоненти — ключовий атрибут RecyclerViews, що провадить так багато функціональності з такою простотою. Якщо ви бажаєте, щоб ваші колекції були гнучкими, та викликали деяке захопленя, тоді не шукайте деінде далі ніж всемогутній RecyclerView.
Фінальний проект для цього туторіала доступний тут.
Якщо ви бажаєте вивчити більше щодо RecyclerViews, перевірте документацію Android, щоб побачити, що він може робити. Погляньте на бібліотеку підтримки щодо RecyclerViews, щоб зрозуміти, як він робить на старіших пристроях. Якщо ви бажаєте, щоб все пасувало до специфікації матеріального дизайну, тоді погляньте на специфікацію дизайну компоненту списку.
Приєднуйтесь до нас на форумах, щоб обговорити цей туторіал, та ваші надбання, що ви винайшли, роблячі з RecylerViews!
До наступної зустрічі, космічний бродяга!